/* [n_msiexec]
 *********************
 * Things to remember:
 *
 * - detail is threadsafe, use caution, all changes will be seen by all threads; recommend: only use to .get data never .set
 * - global variables are global to all threads, use load & unload to initialize & free up memory
 * - use global isInit to initialize global variables requiring detail to be setup
 * - when using global isInit, the code must be mutex protected so all threads do not run the init, just one
 * - inter-thread coordination can be coordinated via the provided mutex
 */
#include <process.h>
#include <stdio.h>
#include <direct.h>

#define DLL_EXPORT
#include "start.h"
#include "Database.h"
#include "Exec.h"

#ifdef _WIN32
#ifdef _DEBUG
	#include <csignal> // or signal.h if C code

	#define _CRTDBG_MAP_ALLOC
	#include <stdlib.h>
	#include <crtdbg.h>
	#undef THIS_FILE
	static char THIS_FILE[] = __FILE__;
	#define new       new( _NORMAL_BLOCK, __FILE__, __LINE__)
	#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
#endif

#include <iostream>
using namespace std;

#ifdef __cplusplus
extern "C" {
#endif	/*__cplusplus*/


// default global variables within dll
ParamManager *supported = NULL;
// module-specific global variables within dll
bool isInit = false;
Database *db = NULL;


// called after the module is loaded, useful to load global variables; Returns supported actions as a ParamManager*
DECLDIR ParamManager* load(void) {
	supported = new ParamManager();
	supported->setOverwrite(false);
	supported->set("action", "msiexec");

	return supported;
}


// called just before the module is unloaded, useful to unload global variables
DECLDIR void unload(void) {
	if(supported) {
		delete supported;
	}
	if(db) {
		delete db;
	}
}


void init(HANDLE mutex, ParamManager &detail) {
	MYSQL_RES *rUpdate = NULL;

	// ensure we only init once
	if(!isInit) {
		char query[DEFAULT_BUFLEN];

		// Instance Table
		db = new Database(
					detail.get("db_user"), detail.get("db_pass"),
					detail.get("db_name"),
					detail.get("db_host"), atoi(detail.get("db_port")));

/*		// take action db connection failed
		if(&(db->conn) == NULL) {
			return;
		}
*/
		// re-establish mysql connection if lost
		bool connect = true;
		mysql_options(&(db->conn), MYSQL_OPT_RECONNECT, &connect);
		//mysql_options(&(db->conn), INNODB_AUTOINC_LOCK_MODE, &connect);
		mysql_options(&(db->conn), MYSQL_INIT_COMMAND, "innodb_autoinc_lock_mode=0");

		sprintf(query, "CREATE TABLE IF NOT EXISTS n_msiexec (");
		strcat(query, "id INT NOT NULL AUTO_INCREMENT PRIMARY KEY");
		strcat(query, ", CSName VARCHAR(16) UNIQUE");
		strcat(query, ", OSName VARCHAR(256)");
		strcat(query, ", Caption VARCHAR(256)");
		strcat(query, ", Commandline VARCHAR(256)");
		strcat(query, ", Processid INT DEFAULT 0");
		strcat(query, ", Sessionid INT DEFAULT 0");
		strcat(query, ", Executablepath VARCHAR(256)");
//		strcat(query, ", ExecutionState VARCHAR(256)");
		strcat(query, ", CreationDate VARCHAR(256)");
		strcat(query, ", isPing INT DEFAULT 0");
		strcat(query, ", isIgnore INT DEFAULT 0");
//		strcat(query, ", track_id INT DEFAULT 0");
		strcat(query, ")");

		// thread-safe mysql call
		rUpdate = db->threadsafe_query(mutex, query);
		// free up the result from the mysql query
		mysql_free_result(rUpdate);

		isInit = true;
	}
}


bool isPing(char *netbios) {
	char *buffer;
	char cmd[DEFAULT_BUFLEN];

	// form ping command
	sprintf(cmd, "ping -n 1 %s", netbios);
	// execute ping
	Exec proc(cmd);

	// process output
	if(proc.isOutput()) {
		// get the next line of output
		while(buffer = proc.getNext()) {
//			printf("buffer: '%s'\n", buffer);

			// string to check for if successful
			if(strstr(buffer, "time=")) {
				return true;
			}
			// string to check for if successful
			if(strstr(buffer, "time<")) {
				return true;
			}
		}
	}
	return false;
}


// sets a value in the database
bool setInt(HANDLE mutex, char *id, char *data, char *value) {
	MYSQL_RES *rUpdate;
	char query[DEFAULT_BUFLEN];

	// Increase the track_id
	sprintf(query, "UPDATE n_msiexec SET %s=%s WHERE id = %s",
			data, value, id);
#ifdef _DEBUG
//	printf("query is '%s'\n", query);
#endif /* _DEBUG */

	// thread-safe mysql call
	rUpdate = db->threadsafe_query(mutex, query);
	// free up the result from the mysql query
	mysql_free_result(rUpdate);

	// always return true for now
	return true;
}


// value string passed in must have quotes around it
bool setString(HANDLE mutex, char *id, char *data, char *value) {
	MYSQL_RES *rUpdate;
	char query[DEFAULT_BUFLEN];

	// Increase the track_id
	sprintf(query, "UPDATE n_msiexec SET %s=\"%s\" WHERE id = %s",
			data, value, id);
#ifdef _DEBUG
	printf("query is '%s'\n", query);
#endif /* _DEBUG */

	// thread-safe mysql call
	rUpdate = db->threadsafe_query(mutex, query);
	// free up the result from the mysql query
	mysql_free_result(rUpdate);

	// always return true for now
	return true;
}


DECLDIR void start(HANDLE mutex, ParamManager &detail, char *index) {
	MYSQL_RES *rSelect = NULL;
	MYSQL_RES *rUpdate = NULL;
	MYSQL_ROW row;
	char query[DEFAULT_BUFLEN];
	char netbios[DEFAULT_BUFLEN];
	char next_line[DEFAULT_BUFLEN];

	// mutex protected init sequence
	WaitForSingleObject(mutex, INFINITE);	// Request ownership of mutex
	init(mutex, detail);				// Init sequence
	ReleaseMutex(mutex);					// Release mutex for other threads

/*	// take action db connection failed
	if(&(db->conn) == NULL) {
		printf("mysql_real_connect failed\n");
		// free up mysql thread-specific memory
		mysql_thread_end();
		return;
	}
*/

	// load up the current working directory
	char data[DEFAULT_BUFLEN];
	_getcwd(data, DEFAULT_BUFLEN);

	// check cmd for action to take
	if(!strcmp(detail.get("action"), "load")) {
		// open up handle to file
		char tmp[DEFAULT_BUFLEN];
		sprintf(tmp, "%s\\nodes\\n_msiexec\\%s", data, detail.get("filename"));
		FILE *source = fopen(tmp, "r");
		if(source == NULL) {
			printf("[msiexec] unable to open file, abort\n");
			return;
		}

#ifdef _DEBUG
		printf("[msiexec] Loading data from:\n[msiexec] %s\n", tmp);
#endif /* _DEBUG */

		// work with file data
		char *ptr;
		while(fgets(next_line, DEFAULT_BUFLEN, source)) {
			// debug out some information
//			printf("next: %s\n", next_line);

			// parse out netbios, its the 3rd item in the csv file
			ptr = strtok(next_line, ",");
			if(ptr == NULL) {
				// this isn't a typical csv line, skip to next line (if there is one)
				continue;
			}

			// pop off next token
			ptr = strtok(NULL, ",");
			if(ptr == NULL) {
				// this isn't a typical csv line, skip to next line (if there is one)
				continue;
			}

			// pop off the netbios
			ptr = strtok(NULL, ",");
			if(ptr == NULL) {
				// this isn't a typical csv line, skip to next line (if there is one)
				continue;
			}

			// special case
			if(!strcmp(ptr, "Netbios_Name0")) {
				// this is a header line, ignore it
				continue;
			}

			// insert this new netbios name (ignoring value if it already exists)
			sprintf(query, "INSERT IGNORE INTO n_msiexec (CSName) VALUES ('%s')", ptr);

			// thread-safe mysql call
			rUpdate = db->threadsafe_query(mutex, query);
			// free up the result from the mysql query
			mysql_free_result(rUpdate);
		}

		// close file handle
		fclose(source);
	}
	else if(!strcmp(detail.get("action"), "process")) {
		// Perform database query and display info
		BIGNUM *bIndex = BN_new();
		BN_dec2bn(&bIndex, index);
		BN_add_word(bIndex, 1);
		char *sindex = BN_bn2dec(bIndex);
		BN_free(bIndex);
		sprintf_s(query, DEFAULT_BUFLEN, "SELECT id, CSName FROM n_msiexec WHERE id = %s AND isIgnore = 0",
					sindex);
//		printf("query is: '%s'\n", query);

		// thread-safe mysql call
		rSelect = db->threadsafe_query(mutex, query);

		if(rSelect) {
			while((row = mysql_fetch_row(rSelect))) { 
				sprintf(netbios, "%s", row[1]);

//				printf("[msiexec] thread, netbios = %s\n", netbios);

				// first make sure the netbios pings
				if(!isPing(netbios)) {
					// does not ping, skip for now
#ifdef _DEBUG
					printf("[msiexec] ping failed, netbios = %s, skipping for now\n", netbios);
#endif /* _DEBUG */
					continue;
				}

#ifdef _DEBUG
				printf("[msiexec] ping ok, netbios = %s\n", netbios);
#endif /* _DEBUG */

				// remember ping worked
				setInt(mutex, sindex, "isPing", "1");

				// perform wmi query (via powershell script for now)
				char cmd[DEFAULT_BUFLEN];
				sprintf(cmd, "powershell %s\\nodes\\n_msiexec\\getdata.ps1 %s", data, netbios);	// data = current working directory
//				printf("cmd = '%s'\n", cmd);
				Exec proc(cmd);

				// process output
				char *buffer;
				if(proc.isOutput()) {
					// boolean to help filter out a failed query
					bool query_worked = false;

					// get the next line of output
					while(buffer = proc.getNext()) {

						// skip blank lines
						if(strlen(buffer) == 0) {
							continue;
						}

						// lines to filter out
						if(strstr(buffer, "CSName")) {
							// ignore header line
							query_worked = true;
							continue;
						}
						if(strstr(buffer, "msiexec.exe /V")) {
							// ignore line which starts the msi service
							continue;
						}
						if(!query_worked) {
							// if we have not detected the header line yet, then we ignore the line
							continue;
						}

						// process the data provided in this line
						char *ptr;
						char escaped[DEFAULT_BUFLEN];

						// next item
						ptr = strtok(buffer, ",");
						if(ptr == NULL) {
							// should not occur
							continue;	// move to next line
						}
#ifdef _DEBUG
						printf("next item = %s\n", ptr);
#endif /* _DEBUG */

						// next item
						ptr = strtok(NULL, ",");
						if(ptr == NULL) {
							// should not occur
							continue;	// move to next line
						}
						// chop off lead and following quotes
						ptr += 1;
						ptr[strlen(ptr) - 1] = '\0';
						// protect special characters
						mysql_escape_string(escaped, ptr, strlen(ptr));
						// temporarily convert BIGINT id to char*
						setString(mutex, sindex, "OSName", escaped);
#ifdef _DEBUG
						printf("next item = %s\n", ptr);
#endif

						// next item
						ptr = strtok(NULL, ",");
						if(ptr == NULL) {
							// should not occur
							continue;	// move to next line
						}
						// chop off lead and following quotes
						ptr += 1;
						ptr[strlen(ptr) - 1] = '\0';
						// protect special characters
						mysql_escape_string(escaped, ptr, strlen(ptr));
						// temporarily convert BIGINT id to char*
						setString(mutex, sindex, "Caption", escaped);
#ifdef _DEBUG
						printf("next item = %s\n", ptr);
#endif

						// next item
						ptr = strtok(NULL, ",");
						if(ptr == NULL) {
							// should not occur
							continue;	// move to next line
						}
						// chop off lead and following quotes
						ptr += 1;
						ptr[strlen(ptr) - 1] = '\0';
						// protect special characters
						mysql_escape_string(escaped, ptr, strlen(ptr));
						// temporarily convert BIGINT id to char*
						setString(mutex, sindex, "Commandline", escaped);
#ifdef _DEBUG
						printf("next item = %s\n", ptr);
#endif

						// next item
						ptr = strtok(NULL, ",");
						if(ptr == NULL) {
							// should not occur
							continue;	// move to next line
						}
						// chop off lead and following quotes
						ptr += 1;
						ptr[strlen(ptr) - 1] = '\0';
						// protect special characters
						mysql_escape_string(escaped, ptr, strlen(ptr));
						// temporarily convert BIGINT id to char*
						setInt(mutex, sindex, "Processid", escaped);
#ifdef _DEBUG
						printf("next item = %s\n", ptr);
#endif

						// next item
						ptr = strtok(NULL, ",");
						if(ptr == NULL) {
							// should not occur
							continue;	// move to next line
						}
						// chop off lead and following quotes
						ptr += 1;
						ptr[strlen(ptr) - 1] = '\0';
						// protect special characters
						mysql_escape_string(escaped, ptr, strlen(ptr));
						// temporarily convert BIGINT id to char*
						setInt(mutex, sindex, "Sessionid", escaped);
#ifdef _DEBUG
						printf("next item = %s\n", ptr);
#endif

						// next item
						ptr = strtok(NULL, ",");
						if(ptr == NULL) {
							// should not occur
							continue;	// move to next line
						}
						// chop off lead and following quotes
						ptr += 1;
						ptr[strlen(ptr) - 1] = '\0';
						// protect special characters
						mysql_escape_string(escaped, ptr, strlen(ptr));
						// temporarily convert BIGINT id to char*
						setString(mutex, sindex, "Executablepath", escaped);
#ifdef _DEBUG
						printf("next item = %s\n", ptr);
#endif
						
/*						// next item
						ptr = strtok(NULL, ",");
						if(ptr == NULL) {
							// should not occur
							continue;	// move to next line
						}
						tmp = BN_bn2dec(todo->id);
						setString(todo->hMutex, tmp, "ExecutionState", ptr);
						OPENSSL_free(tmp);
						printf("next item = %s\n", ptr);
*/
						// next item
						ptr = strtok(NULL, ",");
						if(ptr == NULL) {
							// should not occur
							continue;	// move to next line
						}
						// chop off lead and following quotes
						ptr += 1;
						ptr[strlen(ptr) - 1] = '\0';
						// protect special characters
						mysql_escape_string(escaped, ptr, strlen(ptr));
						// temporarily convert BIGINT id to char*
						setString(mutex, sindex, "CreationDate", escaped);
#ifdef _DEBUG
						printf("next item = %s\n", ptr);
#endif

						// set to ignore this item in the future
						setInt(mutex, sindex, "isIgnore", "1");

						// free up resources
						OPENSSL_free(sindex);
					}
				}
			}
			
			// free up the result form the mysql query
			mysql_free_result(rSelect);
		}
		else {
			// query failed or, there were no results such as in the case of isIgnore = 1
			printf("[Thread::n_msiexec] db query failed\r\n");
		}
	}

	// free up mysql thread-specific memory
	mysql_thread_end();
}


#ifdef __cplusplus
}
#endif	/*__cplusplus */